import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.FPSAnimator;

/**
 * Project 1 Phase 2 CSS 491 - Winter 2012
 * 
 * @author Shawn Paul Smith - ssmith35@uw.edu
 * @author Russ Lindsey - colssanio@gmail.com
 * with code from Demo4 and Demo9
 */

public class Proj1Phase2 implements GLEventListener {
	private static SceneGraphNode sceneGraphRoot;
	private static boolean pickNextFrame;
	private static Point pickedPoint = new Point();
	private static double left, right, top, bottom;
	private static double near = -1;
	private static double far = 1;
	private static int displayListID = -1;
	private static PhysicsObject pickedObject;
	private static final float DEFAULT_PIXEL_SCALE = 128;
	private static List<PhysicsObject> objects = new ArrayList<PhysicsObject>();
	private static ObjectAttacher attacher;
	private static final int TARGET_FPS = 60;
	private static float pixelScale = DEFAULT_PIXEL_SCALE; // [pixelScale]
															// pixels = 1 unit

	public static void main(String[] args) {
		GLProfile.initSingleton();
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				(new Proj1Phase2()).createAndShowGUI();
			}
		});
	}

	private void createAndShowGUI() {
		// Fix for background flickering
		// System.setProperty("sun.awt.noerasebackground", "true");

		GLProfile profile = GLProfile.getDefault();
		GLCapabilities capabilities = new GLCapabilities(profile);
		final GLCanvas canvas = new GLCanvas(capabilities);

		canvas.addGLEventListener(this);
		canvas.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				// right click on a selected object to remove it
				if (SwingUtilities.isRightMouseButton(e) && pickedObject != null) {
					attacher.detachObject(pickedObject);
				} else {
					pickNextFrame = true;
					pickedPoint.setLocation(e.getX(), e.getY());
				}
			}

			public void mouseReleased(MouseEvent e) {
				pickedObject = null;
			}
		});
		
		canvas.addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseDragged(MouseEvent e) {
				float scale = 2f / pixelScale;
				float dx = (float) (e.getX() - pickedPoint.x) * scale;
				float dy = (float) (pickedPoint.y - e.getY()) * scale;
				if (pickedObject != null) {
					pickedObject.position.x += dx;
					pickedObject.position.y += dy;
					pickedObject.velocity.x = 0;
					pickedObject.velocity.y = 0;
				}
				pickedPoint.setLocation(e.getX(), e.getY());
			}
		});
		
		//create a object to attach or detach objects to the physics model.
		attacher = new ObjectAttacher() {
			@Override
			public void attachObject(PhysicsObject object) {
				if (object.renderable != null)
					sceneGraphRoot.addChild(object.renderable);
				objects.add(object);
			}
			
			@Override
			public void detachObject(PhysicsObject object) {
				if (object.renderable != null)
					sceneGraphRoot.removeChild(object.renderable);
				int index = objects.indexOf(object);
				if (index == -1)
					throw new IllegalArgumentException();
				objects.set(index, objects.get(objects.size()-1));
				objects.remove(objects.size()-1);
			}
		};
		sceneGraphRoot = new SceneGraphNode(false);
		sceneGraphRoot.addChild(new Grid());
		
		//halfspaces
		attacher.attachObject(new HalfSpace(new Vector2f(-5, 0), new Vector2f(1, 0)));
		attacher.attachObject(new HalfSpace(new Vector2f(0, -5), new Vector2f(0, 1)));
		attacher.attachObject(new HalfSpace(new Vector2f(5, 0), new Vector2f(-1, 0)));
		attacher.attachObject(new HalfSpace(new Vector2f(0, 5), new Vector2f(0, -1)));
		
		// Add independent SceneGraphNode representing all the HalfSpaces.
		sceneGraphRoot.addChild(new SceneGraphNode(false) {
			public void renderGeometry(GLAutoDrawable drawable) {
				GL2 gl = drawable.getGL().getGL2();	
				gl.glColor3f(1, 1, 1);
				gl.glBegin(GL.GL_LINE_LOOP);
				gl.glVertex2f(-5, -5);
				gl.glVertex2f(5, -5);
				gl.glVertex2f(5, 5);
				gl.glVertex2f(-5, 5);
				gl.glEnd();
			}
		});

		JFrame appFrame = new JFrame("Project 1 Phase 2");
		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.setPreferredSize(new Dimension(1024, 768));
		appFrame.setMinimumSize(new Dimension(256, 256));

		appFrame.setJMenuBar(new ShapeMenuBar(attacher));
		appFrame.add(canvas);
		appFrame.pack();
		//if (Toolkit.getDefaultToolkit().isFrameStateSupported(
		//		JFrame.MAXIMIZED_BOTH))
		//	appFrame.setExtendedState(appFrame.getExtendedState()
		//			| JFrame.MAXIMIZED_BOTH);
		appFrame.setVisible(true);
	}
	
	public void display(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT);
		if (pickNextFrame) {
			GLU glu = GLU.createGLU(gl);
			int viewport[] = new int[4];
			gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);

			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPushMatrix();
			gl.glLoadIdentity();
			// Handle selection, look only around cursor
			glu.gluPickMatrix(pickedPoint.x,
					(double) (viewport[3] - pickedPoint.y), 1, 1, viewport, 0);
			gl.glOrtho(left, right, bottom, top, near, far);
			gl.glMatrixMode(GL2.GL_MODELVIEW);

			List<PhysicsObject> pickList = sceneGraphRoot.getPicked(drawable);
			if (!pickList.isEmpty()) {
				pickedObject = pickList.get(0);
			}

			gl.glMatrixMode(GL2.GL_PROJECTION);
			gl.glPopMatrix();
			gl.glMatrixMode(GL2.GL_MODELVIEW);
			pickNextFrame = false;
			displayListID++;
			gl.glNewList(displayListID, GL2.GL_COMPILE_AND_EXECUTE);
			gl.glEndList();
		}
		if (displayListID == -1) {
			displayListID = 1;
			gl.glNewList(displayListID, GL2.GL_COMPILE_AND_EXECUTE);
			gl.glEndList();
			sceneGraphRoot.render(drawable);
		} else {
			gl.glCallList(displayListID);
		}
		
		for (PhysicsObject object : objects) {
			if (object != pickedObject)
				object.updateState(1f / TARGET_FPS);
		}
		
		boolean noCollisions = false;
		for (int repeat = 0; repeat < 10 && !noCollisions; repeat++) {
			noCollisions = true;
			for (int i = 0; i < objects.size(); i++) {
				PhysicsObject a = objects.get(i);
				for (int j = i + 1; j < objects.size(); j++) {
					PhysicsObject b = objects.get(j);
					CollisionInfo cInfo = a.getCollision(b);
					if (cInfo != null) {
						noCollisions = false;
						a.resolveCollision(b, cInfo);
					}
				}
			}
		}
		for (PhysicsObject object : objects)
			object.updateRenderable();
		
		sceneGraphRoot.render(drawable);
	}

	public void dispose(GLAutoDrawable drawable) {
	}

	public void init(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		gl.glClearColor(0, 0, 0, 0);
		IntBuffer selectBuffer = Buffers.newDirectIntBuffer(3);
		gl.glSelectBuffer(selectBuffer.capacity(), selectBuffer);
		FPSAnimator fps = new FPSAnimator(drawable, TARGET_FPS);
		fps.start();
	}

	public void reshape(GLAutoDrawable drawable, int x, int y, int width,
			int height) {
		GL2 gl = drawable.getGL().getGL2();

		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();

		right = (float) width / pixelScale;
		top = (float) height / pixelScale;
		left = -right;
		bottom = -top;

		gl.glOrtho(left, right, bottom, top, near, far);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}
}
